﻿using System;
using FakeItEasy;
using NJabber.Client.StanzaFactories;
using NJabber.Stream.Elements;
using NUnit.Framework;

namespace NJabber.Client
{
    [TestFixture]
    public class RosterSynchronizerTests
    {
        private RosterCache rosterCache;
        private IXmppConnection connection;
        private IRosterStanzaFactory factory;
        private IRosterUpdater updater;
        private RosterSynchronizer synchronizer;

        [SetUp]
        public void SetUp()
        {
            connection = A.Fake<IXmppConnection>();
            A.CallTo(() => connection.Jid).Returns("client@jid.com");
            factory = new RosterStanzaFactory();
            updater = A.Fake<IRosterUpdater>();
            synchronizer = new RosterSynchronizer(connection, factory, updater);
            rosterCache = new RosterCache(synchronizer);
        }

        [Test]
        public void Sync_ReceivedRosterResult_UpdatesRoster()
        {
            IqStanza rosterResult = CreateRosterResult();

            synchronizer.ProcessStanza(A.Dummy<StandartClientRuntime>(), rosterResult);
            synchronizer.Sync(rosterCache.Changes);

            A.CallTo(() => updater.Update(rosterCache.Changes)).MustHaveHappened();
        }

        [Test]
        public void Sync_ReceivedRosterResultAndClosedConnection_DoesNothing()
        {
            IqStanza rosterResult = CreateRosterResult();

            synchronizer.ProcessStanza(A.Dummy<StandartClientRuntime>(), rosterResult);
            SetConnectionState(ConnectionState.Closed);
            synchronizer.Sync(rosterCache.Changes);

            A.CallTo(() => connection.Stream.Write(A<Stanza>._)).MustNotHaveHappened();
        }

        [Test]
        public void Sync_ReceivedRosterResultClosedAndOpenedConnection_SendsRosterRequest()
        {
            Stanza expectedStanza = CreateRosterRequest();
            IqStanza rosterResult = CreateRosterResult();

            synchronizer.ProcessStanza(A.Dummy<StandartClientRuntime>(), rosterResult);
            SetConnectionState(ConnectionState.Closed);
            connection.Closed += Raise.With(connection, EventArgs.Empty).Now;
            SetConnectionState(ConnectionState.Opened);

            synchronizer.Sync(rosterCache.Changes);

            A.CallTo(() => connection.Stream.Write(expectedStanza)).MustHaveHappened();
            A.CallTo(() => updater.Update(rosterCache.Changes)).MustNotHaveHappened();
        }

        private IqStanza CreateRosterResult()
        {
            IqStanza result = new IqStanza(IqStanzaTypes.Result);
            result.AddQueryElement(XmppNamespaces.Roster)
                  .AddRosterItem("some-item", "some-item@jid.org");
            return result;
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Constructor_NullParameter_ThrowsException()
        {
            RosterSynchronizer synchronizer = new RosterSynchronizer(null, null, null);
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Sync_NullParmeter_ThrowsException()
        {
            synchronizer.Sync(null);
        }

        [Test]
        public void Sync_ConnectionDoesNotOpened_NothingWritesToStream()
        {
            SetConnectionState(ConnectionState.Closed);
            
            synchronizer.Sync(rosterCache.Changes);

            A.CallTo(() => connection.Stream.Write(A<Stanza>._)).MustNotHaveHappened();
        }

        [Test]
        public void Sync_NoRosterChangesAndConnectionOpened_SendsRosterRequest()
        {
            SetConnectionState(ConnectionState.Opened);
            Stanza expectedStanza = CreateRosterRequest();

            synchronizer.Sync(rosterCache.Changes);

            A.CallTo(() => connection.Stream.Write(expectedStanza)).MustHaveHappened();
        }

        [Test]
        public void Sync_MethodCallTwoTimesWithoutRosterChanges_SendsRosterRequestOnce()
        {
            SetConnectionState(ConnectionState.Opened);

            synchronizer.Sync(rosterCache.Changes);
            synchronizer.Sync(rosterCache.Changes);

            A.CallTo(() => connection.Stream.Write(A<Stanza>._)).MustHaveHappened(Repeated.Exactly.Once);
        }

        [Test]
        public void Sync__SendsRosterRequest()
        {
            SetConnectionState(ConnectionState.Opened);

            synchronizer.Sync(rosterCache.Changes);
            connection.Closed += Raise.With(connection, EventArgs.Empty).Now;
            SetConnectionState(ConnectionState.Closed);
            SetConnectionState(ConnectionState.Opened);
            synchronizer.Sync(rosterCache.Changes);

            A.CallTo(() => connection.Stream.Write(A<Stanza>._)).MustHaveHappened(Repeated.Exactly.Times(2));
        }

        private void SetConnectionState(ConnectionState state)
        {
            A.CallTo(() => connection.State).Returns(state);
        }

        private Stanza CreateRosterRequest()
        {
            return factory.CreateRosterRequest("client@jid.com");
        }
    }
}
